require 'hyper_resource'

class Seiso::ImportMaster

  # Base importer for type-specific importers.
  #
  # Subclasses must set the @repo_resource variable to point to the type's repository resource.
  #
  # Additionally, subclasses must provide the following type-specific callback implementations:
  #
  # - to_search_params(doc_item, context) : Returns a hash containing HTTP params for the resource search.
  # - find_resource(search_params) : Takes the search params above and returns the corresponding resource.
  #
  # If the type in question has child resources (e.g., service instances have IP address roles and service instance
  # ports), then subclasses should provide the following callback implementations to handle them:
  #
  # - context_for(parent_resource, parent_context) : Returns a new context to support importing child resources.
  # - import_children_of(doc_item, context) : Imports the child resources.
  #
  # See service_instance_importer.rb for an example that includes all four callbacks.
  #
  # Author:: Willie Wheeler
  # Copyright:: Copyright (c) 2014-2016 Expedia, Inc.
  # License:: Apache 2.0
  class Importers::BaseImporter

    # Imports a list of document items into Seiso.
    #
    # - items : Items in document format
    # - context : Import context
    def import_items(doc_items, context)
      # TODO Perform orphan deletion here, centrally, and recursively?
      doc_items.each { |doc_item| import_item(doc_item, context) }
    end

    # Imports a single document item into Seiso. Returns the imported item's URI
    #
    # - item : Item in document format
    # - context : Import context
    def import_item(doc_item, context)
      data = mapper.map(doc_item, context)
      search_params = to_search_params(doc_item, context)
      begin
        resource = find_resource search_params

        # In the Seiso API, PUT will leave any unspecified associations alone. So for example we can handle node health
        # status and rotation status like this. When we have node build version, though, we will have to do something
        # else, like turn it into an association or else PATCH here. [WLW]
        rest_util.put(resource, data, search_params)

      rescue HyperResource::ClientError => e
        status = e.response.status
        body = e.response.body
        fail "Client error: status=#{status}, body=#{body}" unless status == 404

        # Seiso API returns the created resource in the response body, so grab that.
        # TODO Clean up this implementation. E.g. don't used nested exception handling, and don't create the logger
        # here. [WLW]
        begin
          resource = rest_util.post(repo_resource, data, search_params)
        rescue HyperResource::ClientError => e2
          # HTTP 409 is typical. It happens when client references nonexistent entities.
          status2 = e2.response.status
          body2 = e2.response.body
          log = Util::Logger.new "BaseImporter"
          log.warn "Client error: status=#{status2}, body=#{body2}. Failed to import item."
          return
        end
      end

      # ================================================================================================================
      # FIXME For puts, this is putting the *old* resource on the context, which isn't what we want! [WLW]
      # ================================================================================================================

      if respond_to?(:import_children_of)
        new_context = respond_to?(:context_for) ? context_for(resource, context) : {}
        # FIXME For NodeIpAddress, this is returning a resource whose self link is /search/findByNodeNameAndIpAddress,
        # and we can't PUT to that.
        import_children_of(doc_item, new_context)
      end

      resource.href
    end

    protected

    attr_accessor :api
    attr_accessor :rest_util
    attr_accessor :mapper
    attr_accessor :repo_resource
  end
end
